﻿
namespace HSS.Xaml.UI.Controls
{
	#region using Directives
	using System;
	using System.Diagnostics;
	using System.Windows;
	using System.Windows.Controls;
	using System.Windows.Input;
	using System.Windows.Media;
	using HSS.ComponentModel;
	#endregion

	#region PropertyGridEditor
	/// <summary>
	/// PropertyGridEditor is the base Abstract Class for all other Editors
	/// </summary>
	public abstract class PropertyGridEditor : ContentControl
	{
		#region Fields
		KeyEventHandler keyDownHandler;
		bool skipCommitControlValue;

		/// <summary>
		/// Indicate whether the <see cref="PropertyGridEditor"/> should skip setting the underlying value.
		/// </summary>
		protected bool SkipSetValue
		{
			get { return skipSetValue; }
			set { skipSetValue = value; }
		} private bool skipSetValue;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="descriptor">The <see cref="HSSPropertyDescriptor"/> being edited.</param>
		protected PropertyGridEditor(HSSPropertyDescriptor descriptor)
		{
			if (null == descriptor)
				throw new ArgumentNullException("descriptor");
			this.Descriptor = descriptor;
			this.keyDownHandler = new KeyEventHandler(this.EditControl_KeyDown);
		}
		#endregion

		#region Properties
		/// <summary>
		/// Gets the PropertyGridEntry for this Editor
		/// </summary>
		internal PropertyGridEntry PropertyEntry { get; set; }
		/// <summary>
		/// Gets the associated HSSPropertyDescriptor for this Editor
		/// </summary>
		public HSSPropertyDescriptor Descriptor { get; private set; }
		/// <summary>
		/// Gets if currently in edit mode
		/// </summary>
		public bool IsEditing { get; internal set; }
		/// <summary>
		/// When editing begins should all the text be selected
		/// </summary>
		public bool ShouldSelectAllText { get; set; }
		/// <summary>
		/// Gets or sets the current Object being edited.
		/// </summary>
		public object Object
		{
			get
			{
				return _object;
			}
			set
			{
				object oldValue = _object;
				_object = value;
				this.OnObjectPropertyChanged(oldValue, value);
			}
		} object _object;
		/// <summary>
		/// Gets the PropertyGrid associated with this Item.
		/// </summary>
		private PropertyGrid PropertyGrid
		{
			get { return this.PropertyEntry.PropertyGrid; }
		}
		/// <summary>
		/// Gets or sets the DisplayControl
		/// </summary>
		protected FrameworkElement DisplayControl { get; set; }
		/// <summary>
		/// Gets the inline Editor Control
		/// </summary>
		protected FrameworkElement InlineEditControl
		{
			get
			{
				if (this._inlineEditControl == null)
					this.LoadInlineEditControl();
				return this._inlineEditControl;
			}
		} FrameworkElement _inlineEditControl;
		/// <summary>
		/// Gets if this editor supports the having a Dialog Editor. Default is
		/// false.
		/// </summary>
		public virtual bool SupportsEditDialog
		{
			get { return false; }
		}
		#endregion

		#region Overrides
		/// <summary>
		/// Apply the Controls template.
		/// </summary>
		public override void OnApplyTemplate()
		{
			base.OnApplyTemplate();
			this.BorderThickness = new Thickness(0);
			this.Margin = new Thickness(0);
			this.HorizontalAlignment = HorizontalAlignment.Stretch;
			this.HorizontalContentAlignment = HorizontalAlignment.Stretch;
			this.Height = 18;
		}
		#endregion

		#region Methods

		/// <summary>
		/// Initialize the Editor with the value to be edited
		/// </summary>
		/// <param name="value">The value to be edited</param>
		public void Initialize(object value)
		{
			this.PropertyEntry.ValueControl.Content = this;
			this.DisplayControl = CreateDisplayControl();
			this._object = value;
			this.OnInitialized();
			base.Content = this.DisplayControl;
			this.UpdateDisplayControl(value);
		}

		/// <summary>
		/// This is called prior to executing the default action allowing the
		/// editor to prepare for a new edit cycle.
		/// </summary>
		/// <remarks>
		/// This action is invoked by the user DoubleClicking the Entry Label
		/// </remarks>
		public virtual void PrepareExecuteDefaultAction() { }
		/// <summary>
		/// Begins the editing process, executes the default action for the Editor, such as toggle a boolean value or encrease an enumerated value
		/// </summary>
		/// <remarks>
		/// This action is invoked by the user DoubleClicking the Entry Label
		/// </remarks>
		public virtual void ExecuteDefaultAction() { }
		/// <summary>
		/// Gets the Dialog Edit Button to render if <see cref="SupportsEditDialog"/> is true.
		/// </summary>
		/// <returns>The Dialog Edit Button to render if <see cref="SupportsEditDialog"/> is true; otherwise null;</returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
		public virtual Button GetDialogEditButton()
		{
			return null;
		}

		/// <summary>
		/// Called once the Editor has been initialized
		/// </summary>
		protected virtual void OnInitialized()
		{
		}
		/// <summary>
		/// This method is intended to be overwritten and return the
		/// default inline Editor such as a textbox, combobox or datetimepicker
		/// etc.
		/// </summary>
		/// <returns>The FrameworkElement of the InLineEditControl.</returns>
		protected virtual FrameworkElement CreateInlineEditControl()
		{
			return new LabelTextBox
			{
				Margin = new Thickness(0),
				BorderThickness = new Thickness(0),
				HorizontalAlignment = HorizontalAlignment.Stretch,
				IsReadOnly = false
			};
		}
		/// <summary>
		/// This method is intended to be overwritten and allows the derived 
		/// editor control to initilize the actual inline editor
		/// </summary>
		protected virtual void InitializeInlineEditControl()
		{
		}
		/// <summary>
		/// Updates the inline editor with the new value
		/// </summary>
		/// <param name="newValue">The new value</param>
		protected virtual void UpdateInlineEditControl(object newValue)
		{
			Guard.ArgumentNotNull(newValue, "newValue");
			if (null != this._inlineEditControl)
			{
				if (this._inlineEditControl is TextBox)
				{
					((TextBox)this._inlineEditControl).Text = newValue.ToString();
				}
				else if (this._inlineEditControl is TextBlock)
				{
					((TextBlock)this._inlineEditControl).Text = newValue.ToString();
				}
			}
		}
		/// <summary>
		/// Creates the DisplayControl
		/// </summary>
		/// <returns>The DisplayControl as a FrameworkElement</returns>
		protected virtual FrameworkElement CreateDisplayControl()
		{
			return new LabelTextBox
				{
					Margin = new Thickness(0, 0, 0, 0),
					BorderThickness = new Thickness(0),
					HorizontalAlignment = HorizontalAlignment.Stretch,
					IsTabStop = false,
					IsReadOnly = true
				};
		}
		/// <summary>
		/// Updates the DisplayControl displayed value
		/// </summary>
		/// <param name="newValue">The new value</param>
		protected virtual void UpdateDisplayControl(object newValue)
		{
			((TextBox)this.DisplayControl).Text = this.ValueToString(newValue);
		}
		/// <summary>
		/// Is called when the Object property value changes externally
		/// </summary>
		/// <param name="oldValue">The old value before the change</param>
		/// <param name="newValue">The new value after the change</param>
		protected virtual void OnObjectPropertyChanged(object oldValue, object newValue)
		{
			this.UpdateControlInternal(newValue);
		}
		/// <summary>
		/// Get the an object value as a String
		/// </summary>
		/// <param name="value">The object value to get as string</param>
		/// <returns>The object's value as a string</returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		protected virtual string ValueToString(object value)
		{
			if (null == value)
				return string.Empty;

			string val = string.Empty;
			if (null != value)
			{
				try
				{
					var para = this.Descriptor.GetAttribute<PasswordPropertyTextAttribute>();
					if (para != null && para.Password)
						val = new string((char)0x2022, 8);
					else
					{
						if (value is string)
							return (string)value;
						else
						{
							var tc = this.Descriptor.Converter;
							if (null != tc)
								val = tc.ConvertToString(value);
							else
								val = value.ToString();
						}
					}
				}
				catch (Exception ex)
				{
					Debug.WriteLine(ex.ToString());
				}
			}

			return val;
		}
		/// <summary>
		/// Is called to actually display the inline edit control
		/// </summary>
		protected virtual void OnShowEditControl()
		{
			this.UpdateInlineEditControl(this.Object);
			var inlineEditControl = this.InlineEditControl as Control;
			if (null != inlineEditControl)
				inlineEditControl.Focus();
		}
		/// <summary>
		/// Show a custom Editor Dialog
		/// </summary>
		protected virtual void ShowEditDialogInternal()
		{

		}
		/// <summary>
		/// Is called once the Editor is in editmode. When overwritten
		/// base.OnBeginEdit() should be called.
		/// </summary>
		protected virtual void OnBeginEdit()
		{
			this.IsEditing = true;
		}
		/// <summary>
		/// Is called once the Editor is no longer in editmode. When overwritten
		/// base.OnEndEdit() should be called.
		/// </summary>
		protected virtual void OnEndEdit()
		{
			this.IsEditing = false;
			this.PropertyEntry.EndEdit();
		}

		/// <summary>
		/// Gets the value from the current inline editor
		/// </summary>
		/// <returns>The value</returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
		protected abstract object GetControlValue();


		/// <summary>
		/// Commit the actual value (save)
		/// </summary>
		protected void CommitControlValue()
		{
			if (!this.skipCommitControlValue)
				this.CommitValueInternal();
		}


		private void LoadInlineEditControl()
		{
			this._inlineEditControl = this.CreateInlineEditControl();
			if (this._inlineEditControl != null)
			{
				this.InitializeInlineEditControl();
			}
		}
		private void HookEditControl(FrameworkElement editControl, bool hook)
		{
			if (hook)
			{
				editControl.AddHandler(UIElement.KeyDownEvent, this.keyDownHandler, true);
				editControl.Loaded += new RoutedEventHandler(this.EditControl_Loaded);
			}
			else
			{
				editControl.RemoveHandler(UIElement.KeyDownEvent, this.keyDownHandler);
				editControl.Loaded -= new RoutedEventHandler(this.EditControl_Loaded);
			}
		}
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void CommitValueInternal()
		{
			object controlValue = null;
			try
			{
				if (!SkipSetValue)
				{
					controlValue = this.GetControlValue();
					this.PropertyEntry.SetPropertyValue(controlValue);
				}
				controlValue = this.PropertyEntry.GetPropertyValue();
				this.UpdateDisplayControl(controlValue);
				this._object = controlValue;
			}
			catch (Exception ex)
			{
				try
				{
					controlValue = this.PropertyEntry.GetPropertyValue();
					this.UpdateDisplayControl(controlValue);
					this.UpdateControlInternal(controlValue);
					this._object = controlValue;
				}
				catch { }
				this.ShowError(ex);
			}
		}
		private void UpdateControlInternal(object newValue)
		{
			this.skipCommitControlValue = true;
			try
			{
				if (newValue != null)
					this.UpdateInlineEditControl(newValue);
				this.UpdateDisplayControl(newValue);
			}
			finally
			{
				this.skipCommitControlValue = false;
			}
		}


		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
		internal bool HasFocus()
		{
			for (var obj2 = FocusManager.GetFocusedElement() as DependencyObject; obj2 != null; obj2 = VisualTreeHelper.GetParent(obj2))
			{
				if (object.ReferenceEquals(obj2, this))
					return true;
			}

			return false;
		}
		internal void ShowError(Exception ex)
		{
			if (null == ex) return;
			while (null != ex.InnerException)
				ex = ex.InnerException;

			var errWindow = new PropertyGridError(ex.Message);
			errWindow.Title = this.PropertyEntry.Descriptor.DisplayName;
			errWindow.Show();
		}
		internal void ShowEditDialog()
		{
			this.ShowEditDialogInternal();
		}
		internal void FocusDisplayControl()
		{
			var box = this.DisplayControl as TextBox;
			if (null != box)
			{
				if (!box.IsTabStop)
				{
					box.IsTabStop = true;
					box.Focus();
					box.SelectAll();
					box.LostFocus += new RoutedEventHandler(DisplayControl_LostFocus);
				}
			}
		}


		#endregion

		#region IEditableObject Members
		/// <summary>
		/// Causes the object to enter editing mode.
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		public void BeginEdit()
		{
			if (this.IsEditing)
				return;

			try
			{
				var box = this.DisplayControl as Control;
				if (null != box)
					box.IsTabStop = false;
				if (this.InlineEditControl != null)
				{
					this.HookEditControl(this._inlineEditControl, false);
					this.HookEditControl(this._inlineEditControl, true);
					base.Content = this.InlineEditControl;
				}
				if (this.PropertyGrid.ActiveEditor != null)
					this.PropertyGrid.ActiveEditor.EndEdit();
				this.PropertyGrid.ActiveEditor = this;
				this.PropertyEntry.IsEditorFocused = true;
				this.OnBeginEdit();
			}
			catch (Exception ex)
			{
				this.ShowError(ex);
			}
			//finally
			//{
			//    this.ShouldSelectAllText = false;
			//}
		}
		/// <summary>
		/// Causes the object to leave editing mode and revert to the previous, unedited
		/// value.
		/// </summary>
		public void CancelEdit()
		{
			this.EndEditInternal();
		}
		/// <summary>
		/// Causes the object to leave editing mode and commit the edited value.
		/// </summary>
		public void EndEdit()
		{
			this.EndEditInternal();
			this.CommitControlValue();
		}
		private void EndEditInternal()
		{
			if (this.InlineEditControl != null)
				this.HookEditControl(this._inlineEditControl, false);
			base.Content = this.DisplayControl;
			this.PropertyGrid.ActiveEditor = null;
			this.OnEndEdit();
		}
		#endregion

		#region Event Handlers
		private void EditControl_Loaded(object sender, RoutedEventArgs e)
		{
			this.InlineEditControl.Loaded -= new RoutedEventHandler(this.EditControl_Loaded);
			this.OnShowEditControl();
		}
		private void EditControl_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
		{
			if (Keyboard.Modifiers == ModifierKeys.None)
			{
				switch (e.Key)
				{
					case System.Windows.Input.Key.Tab:
						this.EndEdit();
						e.Handled = true;
						break;

					case System.Windows.Input.Key.Escape:
						this.CancelEdit();
						e.Handled = true;
						break;

					case System.Windows.Input.Key.Enter:
						this.EndEdit();
						e.Handled = true;
						return;
				}
			}
		}
		private void DisplayControl_LostFocus(object sender, RoutedEventArgs e)
		{
			var box = sender as LabelTextBox;
			if (null != box)
			{
				box.LostFocus -= new RoutedEventHandler(DisplayControl_LostFocus);
				box.IsTabStop = false;
			}
		}
		#endregion
	}
	#endregion
}
